package com.mysher.devlib.camera2;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.opengl.GLES11Ext;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;

import androidx.annotation.NonNull;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public class Camera2Helper {

    private String id;
    private Size[] yuvSize;
    private Surface surface;
    private boolean hasCreateSurface;

    private Handler handler;

    private CameraDevice cameraDevice;
    private Camera2DevListener devListener;

    private CameraCaptureSession cameraCaptureSession;
    private Camera2PreviewListener camera2PreviewListener;
    private ImageReader previewImageReader;


    public void injectId(String id) {
        this.id = id;
    }

    public int injectVidPid(int vid, int pid, Context context) {
        List<String> list = CameraIdUtils.getCameraList();
        int index = CameraIdUtils.getIndexOfCamera(vid, pid, list);
        if (index == -1) {
            return -101; //未找到usb摄像头
        }
        /*CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraIdList = cameraManager.getCameraIdList();
            id = cameraIdList[index];
            return true;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }*/
        return checkId(list.size(), index, context);
    }

    public int injectVidPid(int[] vidPids, Context context) {
        List<String> list = CameraIdUtils.getCameraList();
        int index = CameraIdUtils.getIndexOfCamera(vidPids, list);
        if (index == -1) {
            return -101; //未找到usb摄像头
        }
        return checkId(list.size(), index, context);
    }

    private int checkId(int length, int index, Context context) {
        CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraIdList = cameraManager.getCameraIdList();
            if (cameraIdList.length != length) {
                return -102; //摄像头index不匹配
            }
            id = cameraIdList[index];
            return 0;
        } catch (CameraAccessException | ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        return -103;//摄像头id错误

    }

    public boolean initCamera(Context context, Surface surface) {
        if (surface == null) {
            initSurface();
        } else {
            this.surface = surface;
            hasCreateSurface = false;
        }
        CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            //String[] cameraIdList = cameraManager.getCameraIdList();
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
            StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            //yuv 420  libyuv i420 数据
            if (streamConfigurationMap.isOutputSupportedFor(ImageFormat.YUV_420_888)) {
                yuvSize = streamConfigurationMap.getOutputSizes(ImageFormat.YUV_420_888);
            }
            initHandler();
        } catch (CameraAccessException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private void initHandler() {
        if (handler != null) {
            HandlerThread handlerThread = new HandlerThread("camera thread");
            handlerThread.start();
            handler = new Handler(handlerThread.getLooper());
        }
    }

    private void initSurface() {
        SurfaceTexture surfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
        surface = new Surface(surfaceTexture);
        hasCreateSurface = true;
    }

    @SuppressLint("MissingPermission")
    public boolean openCamera(Context context, Camera2DevListener listener) {
        if (yuvSize == null) {
            return false;
        }
        this.devListener = listener;
        CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            cameraManager.openCamera(id, deviceCallback, handler);
            return true;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return false;
    }

    private CameraDevice.StateCallback deviceCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraDevice = camera;
            if (devListener != null) {
                devListener.onOpen();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            if (devListener != null) {
                devListener.onDevDisConnect();
            }
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            if (devListener != null) {
                devListener.onError();
            }
        }

        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            super.onClosed(camera);
            if (devListener != null) {
                devListener.onClosed();
            }
        }
    };

    public Size[] getYuvSize() {
        return yuvSize;
    }

    public void starPreview(Size size, Camera2PreviewListener previewListener) {
        this.camera2PreviewListener = previewListener;
        createCameraSession(size);
    }

    private void createCameraSession(Size previewSize) {
        try {

            previewImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
            previewImageReader.setOnImageAvailableListener(previewListener, handler);
            ArrayList<Surface> outPuts = new ArrayList<>();
            outPuts.add(surface);

            outPuts.add(previewImageReader.getSurface());
            cameraDevice.createCaptureSession(outPuts, sessionCallback, handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.StateCallback sessionCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            cameraCaptureSession = session;
            try {
                CaptureRequest.Builder previewRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                previewRequest.addTarget(surface);
                previewRequest.addTarget(previewImageReader.getSurface());

                session.setRepeatingRequest(previewRequest.build(), null, handler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            camera2PreviewListener.onPreviewFail();
        }
    };

    /*private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

    };*/

    public void stopPreview() {
        if (cameraCaptureSession != null) {
            try {
                cameraCaptureSession.stopRepeating();
                cameraCaptureSession.close();
                previewImageReader = null;
                cameraCaptureSession = null;
                camera2PreviewListener = null;
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    }

    public void stopCamera() {
        stopPreview();
        if (cameraDevice != null) {
            cameraDevice.close();
            cameraDevice = null;
        }
        devListener = null;
    }

    public void release() {
        if (handler != null) {
            handler.getLooper().quit();
            handler = null;
        }
        if (hasCreateSurface) {
            surface.release();
        }
    }

    private byte[] y, u, v;
    private volatile byte[] nv21;

    private void initYuvByte(int width, int height) {
        int ySize = width * height;
        y = new byte[ySize];
        u = new byte[ySize / 2];
        v = new byte[ySize / 2];
        nv21 = new byte[ySize * 3 / 2];
    }

    private ImageReader.OnImageAvailableListener previewListener = reader -> {
        Image image = reader.acquireLatestImage();
        int width = reader.getWidth();
        int height = reader.getHeight();
        if (image == null) {
            return;
        }
        try {
            if (y == null) {
                initYuvByte(width, height);
            }
            Image.Plane[] planes = image.getPlanes();
            Image.Plane yPlane = planes[0];
            Image.Plane uPlane = planes[1];
            Image.Plane vPlane = planes[2];
            ByteBuffer yBuffer = yPlane.getBuffer();
            yBuffer.get(y);
            ByteBuffer uBuffer = uPlane.getBuffer();
            int capacity = uBuffer.capacity();
            uBuffer.get(u, 0, capacity);
            ByteBuffer vBuffer = vPlane.getBuffer();
            capacity = vBuffer.capacity();
            vBuffer.get(v, 0, capacity);
            yuv422ToYuv420sp(y, u, v, nv21, width, height);
            if (camera2PreviewListener != null) {
                camera2PreviewListener.onPreview(nv21, width, height);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            image.close();
        }
    };


    private void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
        System.arraycopy(y, 0, nv21, 0, y.length);
        // 注意，若length值为 y.length * 3 / 2 会有数组越界的风险，需使用真实数据长度计算
        int length = y.length + u.length / 2 + v.length / 2;
        int uIndex = 0, vIndex = 0;
        for (int i = stride * height; i < length; i += 2) {
            nv21[i] = v[vIndex];
            nv21[i + 1] = u[uIndex];
            vIndex += 2;
            uIndex += 2;
        }
    }


    /**
     * Configures the necessary {@link Matrix} transformation to `mTextureView`.
     * This method should be called after the camera preview size is determined in
     * setUpCameraOutputs and also the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    public void configureTransform(TextureView textureView, int viewWidth, int viewHeight, Size previewSize, int rotation) {
        if (null == textureView || null == previewSize) {
            return;
        }
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max(
                    (float) viewHeight / previewSize.getHeight(),
                    (float) viewWidth / previewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180, centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    public String getErrorMsg(int errorCode) {
        if (errorCode == -101) {
            return "获取摄像头错误";
        } else if (errorCode == -102) {
            return "请勿插入2个以上摄像头";
        } else if (errorCode == -103) {
            return "打开摄像头错误";
        } else {
            return "未知错误";
        }
    }
}
